K.Drozynski 134 Report Share Posted 6 hours ago Two classes, identical API: net.WebSocketClient (ws://) and net.WebSocketClientTls (wss://). namespace net { interface WebSocketClient { void connect(string url, [table headers]); void send(string data); void sendBinary(string data); bool isOpen(); void close(); void addEventListener(string eventName, function handler); // alias of registerEvent() void registerEvent(string eventName, function handler); void unregisterEvent(string eventName); // TODO: not callable from Lua yet event connected; event disconnected; event dataReceived; event error; } } // namespace net Connect void connect(string url, [table headers]); url - ws://host[:port]/path for WebSocketClient, wss://... for WebSocketClientTls. headers - a table of header name/value pairs added to the handshake request, e.g. Origin or Authorization. Sec-WebSocket-Protocol is handled specially: its value is split on , and each entry is registered as a requested subprotocol, so it takes part in the library’s subprotocol handling instead of being sent as an opaque header. The request on the wire is unchanged. An entry that is not a valid HTTP token raises a Lua error from connect(). self.sock:connect("wss://broker.example.com/mqtt", { ["Sec-WebSocket-Protocol"] = "mqtt", ["Authorization"] = "Bearer " .. token, }) connected event - fired once the handshake completes. No arguments. disconnected event - fired when the connection is closed by either side. No arguments. Send void send(string data); Sends a text frame. RFC 6455 requires a text payload to be valid UTF-8; if data is not, nothing is sent and the error event fires with a message pointing at sendBinary(). void sendBinary(string data); Sends a binary frame, with no constraint on the bytes. Use this for any protocol that is not UTF-8 text - MQTT over WebSocket, for instance, mandates binary frames. Lua strings are byte strings, so both methods take the same type; the method chosen decides the frame opcode. self.sock:send("hello") -- text frame self.sock:sendBinary(packet) -- binary frame, e.g. an MQTT CONNECT packet above is just a Lua string holding the raw bytes of the protocol frame, assembled with string.char. An MQTT 3.1.1 CONNECT, for example - a 2 byte length prefix before every string field, and a varint Remaining Length after the fixed header byte: -- Remaining Length: 7 payload bits per byte, MSB set while more bytes follow. local function varint(n) local out = {} repeat local byte = n % 128 n = n // 128 if n > 0 then byte = byte + 128 end out[#out + 1] = string.char(byte) until n == 0 return table.concat(out) end -- MQTT string field: 2 byte big-endian length, then the raw bytes. local function mqttString(text) return string.char(#text // 256, #text % 256) .. text end local function connectPacket(clientId, keepAlive) local variableHeader = mqttString("MQTT") -- protocol name .. string.char(0x04) -- protocol level 4 = MQTT 3.1.1 .. string.char(0x02) -- connect flags: clean session .. string.char(keepAlive // 256, keepAlive % 256) local payload = mqttString(clientId) local body = variableHeader .. payload return string.char(0x10) .. varint(#body) .. body -- 0x10 = CONNECT end local packet = connectPacket("hc3-client", 60) -- 10 16 00 04 4D 51 54 54 04 02 00 3C 00 0A 68 63 33 2D 63 6C 69 65 6E 74 self.sock:sendBinary(packet) The same bytes would be rejected by send(): 0x10 and 0x04 are fine, but mqttString lengths and binary flag bytes routinely fall outside UTF-8. A received frame is decoded the same way, with one caveat: a WebSocket frame is not guaranteed to hold exactly one protocol packet. It may hold several, or only part of one, so a dataReceived handler must accumulate bytes and pull off whole packets rather than assume one frame == one packet. self.sock:addEventListener("dataReceived", function(payload, isBinary) self.buffer = self.buffer .. payload -- ... peel complete MQTT packets off self.buffer, keep the remainder end) The same applies in the other direction: one sendBinary() call is one frame, but a broker is free to split or merge packets across frames, so never assume a one-to-one mapping. Receive dataReceived event arguments: payload:string - the frame payload, bytes preserved. isBinary:bool - true for a binary frame, false for a text frame. self.sock:addEventListener("dataReceived", function(payload, isBinary) ... end) Error Event error event is fired when the handshake fails or a send is rejected. Argument: message:string - human readable description. Backwards compatibility sendBinary() is additive and send()'s signature is unchanged, so existing callers are unaffected. dataReceived’s second argument is safe to add: a handler is always a Lua function and Lua discards surplus arguments, so a one-parameter handler is untouched. Only a function(...) varargs handler, or a library function used directly as the handler, would observe it. The Sec-WebSocket-Protocol rerouting keeps the request bytes identical - the requested list is joined with ", ", the same canonical form the raw header used - and the subprotocol echoed back in the server’s response is not validated, so no previously-working handshake can start failing. One edge case is not compatible: a subprotocol containing a character that is not allowed in an HTTP token (a space, /, :) is now rejected and connect() raises a Lua error, where such a value was previously sent verbatim and ignored by the server. A malformed value that used to connect now raises. An empty value now omits the header rather than sending it empty. Remarks Ping/pong, fragmentation and continuation frames are managed internally and are not exposed. Closing the socket happens automatically when the object is finalized by Lua. close() may be called before connected has fired. The socket is then closed as soon as the handshake finishes, and connected is not delivered - only disconnected, once the connection is down. Thanks Like × Link to comment https://forum.fibaro.com/topic/80470-mqtt-over-secure-websockets-documentation/ Share on other sites More sharing options...
Recommended Posts